package com.yubest.demo.anno;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.yubest.demo.bean.LogBean;
import com.yubest.demo.util.SpringUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.common.TemplateParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @Author hweiyu
 * @Description
 * @Date 2022/1/21 9:56
 */
@Aspect
@Component
@Slf4j
public class LogAspect {

    @Pointcut("@annotation(com.yubest.demo.anno.Log)")
    public void pointcut() {

    }

    @Around("pointcut()")
    public Object around(ProceedingJoinPoint pjp) throws Throwable {
        //执行结果
        Object val = null;
        //日志封装对象
        LogBean logBean = prepareLog(pjp);
        try {
            val = pjp.proceed();
            logBean.setResponseData(val);
            return val;
        } catch (Exception e) {
            logBean.setException(e);
        } finally {
            //处理日志
            processLog(logBean);
            if (logBean.getException() != null) {
                throw logBean.getException();
            }
        }
        return val;
    }

    private LogBean prepareLog(ProceedingJoinPoint pjp) {
        MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
        Method targetMethod = methodSignature.getMethod();
        //获取方法的参数名称
        String[] paramNames = new LocalVariableTableParameterNameDiscoverer().getParameterNames(targetMethod);
        //获取方法的参数值
        Object[] paramValues = pjp.getArgs();
        StandardEvaluationContext context = new StandardEvaluationContext();
        Map<String, Object> paramMap = new HashMap<>();
        if (null != paramNames) {
            for (int i = 0; i < paramNames.length; i++) {
                if (paramValues[i] instanceof MultipartFile
                        || paramValues[i] instanceof HttpServletRequest
                        || paramValues[i] instanceof HttpServletResponse) {
                    continue;
                }
                //放入上下文
                context.setVariable(paramNames[i], paramValues[i]);
                //参数
                paramMap.put(paramNames[i], paramValues[i]);
            }
        }
        LogBean logBean = new LogBean();
        logBean.setIsSuccess(true);
        //日志注解
        Log myLog = targetMethod.getAnnotation(Log.class);
        if (null == myLog) {
            logBean.setIsSuccess(false);
            return logBean;
        }
        try {
            logBean.setType(myLog.type());
            logBean.setRequestData(paramMap);
            logBean.setMethodName(methodSignature.getDeclaringTypeName() + "#" + methodSignature.getName());
            //spel解析
            SpelExpressionParser parser = new SpelExpressionParser();
            TemplateParserContext template = new TemplateParserContext();
            if (StringUtils.hasText(myLog.desc())) {
                setVariableBySpringBeans(context, myLog.desc());
                logBean.setLog(parser.parseExpression(myLog.desc(), template).getValue(context, String.class));
            }
            if (StringUtils.hasText(myLog.operator())) {
                setVariableBySpringBeans(context, myLog.operator());
                logBean.setOperator(parser.parseExpression(myLog.operator(), template).getValue(context, String.class));
            }
            if (StringUtils.hasText(myLog.bizNo())) {
                setVariableBySpringBeans(context, myLog.bizNo());
                logBean.setBizNo(parser.parseExpression(myLog.bizNo(), template).getValue(context, String.class));
            }
        } catch (Exception e) {
            logBean.setLog(myLog.desc());
            logBean.setOperator(myLog.operator());
            logBean.setBizNo(myLog.bizNo());
            logBean.setIsSuccess(false);
            log.error("解析操作日志异常", e);
        }
        return logBean;
    }

    private ObjectMapper mapper = new ObjectMapper();

    public void processLog(LogBean logBean) {
        log.info("------开始记录操作日志------");
        try {
            log.info(mapper.writeValueAsString(logBean));
        } catch (JsonProcessingException e) {
            log.error("日志解析异常", e);
        }
        log.info("------结束记录操作日志------");
    }

    /**
     * 获取spel中，spring bean name 的正则 开头 #{#，结尾 .
     */
    private static final Pattern BEAN_NAME_PATTERN = Pattern.compile("(#[{]#[a-zA-Z0-9_$]+[.])");

    public void setVariableBySpringBeans(StandardEvaluationContext context, String ... templates) {
        if (null == templates || templates.length == 0) {
            return;
        }
        for (String template : templates) {
            Matcher matcher = BEAN_NAME_PATTERN.matcher(template);
            while (matcher.find()) {
                String beanName = template.substring(matcher.start() + 3, matcher.end() - 1);
                Object bean = SpringUtil.getBean(beanName);
                if (null != bean) {
                    context.setVariable(beanName, bean);
                }
            }
        }
    }
}
